extern crate piston_window;

use std::path::PathBuf;
use piston_window::*;
use rand::distributions::Uniform;
use rand::prelude::*;

#[derive(PartialEq)]
enum Dir {
    Up,
    Down,
    Left,
    Right,
}

const GRID_WIDTH: i32 = 30;
const SCREEN_WIDTH: i32 = 20;
const SCREEN_HEIGHT: i32 = 20;

#[derive(PartialEq)]
struct Position {
    x: i32,
    y: i32,
}

impl Position {
    pub fn to_grid(&self) -> [f64; 4] {
        [(self.x * GRID_WIDTH) as f64, (self.y * GRID_WIDTH) as f64, GRID_WIDTH as f64, GRID_WIDTH as f64]
    }
}

struct Game {
    pub snake: Vec<Position>,
    pub food: Position,
    move_time: i32,
    timer: i32,
    dir: Dir,
    pub score: i32,
    stop: bool,
}

impl Game {
    pub fn new() -> Game {
        Game {
            snake: vec![Position{x: SCREEN_WIDTH / 2, y: SCREEN_HEIGHT / 2}],
            food: Position{x: 4, y: 4},
            dir: Dir::Left,
            move_time: 50,
            timer: 0,
            score: 0,
            stop: true,
        }
    }

    pub fn update(&mut self) {
        if self.snake_should_move() && self.stop == false {
            if self.collides_with_wall() || self.collides_with_self() {
                self.reset();
            }

            if self.collides_with_food() {
                // self.food.x = (random::<u32>() % SCREEN_WIDTH as u32 * GRID_WIDTH as u32) as i32;
                // self.food.y = (random::<u32>() % SCREEN_HEIGHT as u32 * GRID_WIDTH as u32) as i32;
                self.food = self.generate_food();

                self.snake.push(Position{
                    x: self.snake[self.snake.len() - 1].x,
                    y: self.snake[self.snake.len() - 1].y,
                });

                self.score += 1;
            }

            for i in (1..self.snake.len()).rev() {
                self.snake[i].x = self.snake[i-1].x;
                self.snake[i].y = self.snake[i-1].y;
            }

            match self.dir {
                Dir::Left => self.snake[0].x -= 1,
                Dir::Right => self.snake[0].x += 1,
                Dir::Up => self.snake[0].y -= 1,
                Dir::Down => self.snake[0].y += 1,
            }

        }

        self.timer += 1;
    }

    pub fn press_key(&mut self, button: Button) {
        match button {
            Button::Keyboard(Key::W) if self.dir != Dir::Down => self.dir = Dir::Up,
            Button::Keyboard(Key::S) if self.dir != Dir::Up => self.dir = Dir::Down,
            Button::Keyboard(Key::A) if self.dir != Dir::Right => self.dir = Dir::Left,
            Button::Keyboard(Key::D) if self.dir != Dir::Left => self.dir = Dir::Right,
            Button::Keyboard(Key::Space) => self.pause(),
            _ => {},
        };
    }

    pub fn draw(&self, c :Context, g :&mut G2d) {
        // draw body
        for body in &self.snake {
            rectangle([0.4, 0.4, 0.4, 1.0],
                      body.to_grid(),
                      c.transform, g);
        }

        // draw food
        rectangle([1.0, 0.0, 0.0, 1.0], // red
                  self.food.to_grid(), // rectangle
                  c.transform, g);
    }

    pub fn pause(&mut self) {
        self.stop = !self.stop;
    }

    fn reset(&mut self) {
        self.snake = vec![Position{x: SCREEN_WIDTH / 2, y: SCREEN_HEIGHT / 2}];
        self.food = Position{x: 3, y: 3};
        self.dir = Dir::Left;
        self.score = 0;
        self.timer = 0;
        self.stop = true;
    }

    fn collides_with_food(&self) -> bool {
        self.snake[0].x == self.food.x && self.snake[0].y == self.food.y
    }

    fn collides_with_wall(&self) -> bool {
        self.snake[0].x <= 0 || self.snake[0].x >= SCREEN_WIDTH  ||
            self.snake[0].y <= 0 || self.snake[0].y >= SCREEN_HEIGHT
    }

    fn collides_with_self(&self) -> bool {
        let head = &self.snake[0];
        for pos in &self.snake[1..] {
            if head.x == pos.x && head.y == pos.y {
                return true;
            }
        }
        false
    }

    fn generate_food(&mut self) -> Position {
        let mut rng = thread_rng();
        let die_x = Uniform::from(0..SCREEN_WIDTH);
        let die_y = Uniform::from(0..SCREEN_HEIGHT);

        let mut pos = Position{x: die_x.sample(&mut rng), y: die_y.sample(&mut rng)};

        while self.snake.contains(&pos) {
            pos = Position{x: die_x.sample(&mut rng), y: die_y.sample(&mut rng)};
        }

        pos
    }

    fn snake_should_move(&self) -> bool {
        self.timer % self.move_time == 0
    }
}

fn main() {
    let mut window: PistonWindow = WindowSettings::new("Snake",
                                                       ((SCREEN_WIDTH * GRID_WIDTH) as u32,
                                                        (SCREEN_HEIGHT * GRID_WIDTH) as u32))
        .exit_on_esc(true)
        .build()
        .unwrap_or_else(|e| { panic!("Failed to build PistonWindow: {}", e) });

    let assets = PathBuf::from("/Users/sjy/Library/Fonts/Hack-Regular.ttf");
    let mut glyphs = window.load_font(assets).unwrap();

    let mut game = Game::new();

    while let Some(e) = window.next() {
        if let Some(button) = e.press_args() {
            game.press_key(button);
        }

        game.update();

        window.draw_2d(&e, |c, g, d| {
            clear([0.5, 1.0, 0.5, 1.0], g);

            game.draw(c, g);

            let transform = c.transform.trans(30.0, 30.0);
            text([0.0, 0.0, 0.0, 1.0], 20, format!("Score:{}", game.score).as_str(), &mut glyphs, transform, g).unwrap();
            if game.stop {
                let transform = c.transform.trans(30.0, 60.0);
                text([0.0, 0.0,0.0,1.0], 20, "Press Space to continue.", &mut glyphs, transform, g).unwrap();
            }
            glyphs.factory.encoder.flush(d);
        });
    }
}


#[test]
fn test_loop() {
    for i in (1..10).rev() {
        println!("i={}", i);
    }
}
